package com.ultra.factory;

import com.ultra.constraint.BeforeArrayFill;
import com.ultra.constraint.BeforeArrayInitialization;
import com.ultra.enums.ArgumentTypeEnum;
import com.ultra.exception.UnableResolveTypeException;
import com.ultra.reflect.ReflectUtil;
import com.ultra.sort.ArraySort;
import com.ultra.template.content.AcvContext;
import com.ultra.template.content.Argument;
import com.ultra.validator.Range;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @author hcUltra
 * @description TODO 将生成的组数抽象出来，以便加入多组对照样本
 * @date 2024/4/23 18:39
 **/
@SuppressWarnings("all")
public class RandomArgumentFactory {
    // 根据参数配置 ArgumentsConfig 生成随机参数
    // 返回两组随机参数 args0 ... argsN
    public static Object[] factory(Argument config) throws UnableResolveTypeException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        // 判断类型
        if (config.getArgumentType().equals(ArgumentTypeEnum.PRIMITIVE)) {
            // 递归出口
            // 基本数据类型 -> 返回两个基本数据类型的变量
            return primitiveObjFactory(config);
        } else if (config.getArgumentType().equals(ArgumentTypeEnum.WRAPPER)) {
            // 递归出口
            // Wrapper 类型 -> 返回两个基本数据类型包装类的对象（引用变量）
            return wrapperObjFactory(config);
        } else if (config.getArgumentType().equals(ArgumentTypeEnum.ARRAY)) {
            // 数组类型 -> 返回两个数组对象
            return arrayObjFactory(config);
        } else if (config.getArgumentType().equals(ArgumentTypeEnum.COLLECTION)) {
            // 集合类型 -> 返回两个集合
            return collectionObjFactory(config);
        } else if (config.getArgumentType().equals(ArgumentTypeEnum.MAP)) {
            // TODO 此处会尽量可能构造出两个有size个键值对的Map
            //  如果在给定的范围内无法构造出两个有size个键值对的Map，则直接抛出异常
            //   此处可能引发死循环
            return mapObjFactory(config);
        }
        throw new UnableResolveTypeException("Unknown Type On Constructing Random Parameter");
    }

    /**
     * 调度器
     *
     * @param context 参数配置
     * @return 返回两组参数
     */
    public static Object[][] factorytDispatcher(AcvContext context) throws UnableResolveTypeException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        List<Argument> arguments = context.getArguments();
        Object[][] parameters = new Object[2][arguments.size()];
        int i = 0;
        for (Argument argument : arguments) {
            Object[] objs = factory(argument);
            assert objs != null;
            parameters[0][i] = objs[0];
            parameters[1][i] = objs[1];
            i++;
        }
        // TODO 所有参数都构造完成时 ... 执行一个回调
        return parameters;
    }

    public static Object[] primitiveObjFactory(Argument argument) {
        BasicTypeFactory btf = new BasicTypeFactory(argument);
        return BasicTypeFactory.factory.get(argument.getType().toString()).apply(btf);
    }

    public static Object[] wrapperObjFactory(Argument argument) {
        BasicTypeFactory btf = new BasicTypeFactory(argument);
        return BasicTypeFactory.factory.get(argument.getType().toString().substring(6)).apply(btf);
    }

    public static Object[] arrayObjFactory(Argument argument) throws UnableResolveTypeException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        Object[] arrays = new Object[2];

        if (argument.getArrayInitialization() != null) {
            BeforeArrayInitialization callback = argument.getArrayInitialization();
            callback.beforeArrayInitialization(argument);
        }
        int size = argument.getFixSize() == -1 ? (int) Range.getNumberMinToMax(argument.getSize()) : argument.getFixSize();
        if (size < 0) {
            return null;
        }
        Class<?> arrayType = Class.forName(argument.getClassName());
        Class<?> componentType = arrayType.getComponentType();
        arrays[0] = Array.newInstance(componentType, size);
        arrays[1] = Array.newInstance(componentType, size);
        int index = 0;

        if (argument.getBeforeArrayFill() != null) {
            // 执行数组填充前的回调
            BeforeArrayFill callback = argument.getBeforeArrayFill();
            callback.beforeArrayFill(argument);
        }

        for (int i = 0; i < size; i++) {
            Object[] objs = factory(argument.getChild()[0]);
            assert objs != null;

            Array.set(arrays[0], index, objs[0]);
            Array.set(arrays[1], index, objs[1]);

            index++;
        }
        // TODO 填充完毕后

        // TODO start

        // TODO end


        if (argument.getOrder() != null && argument.getOrder().getIsSorted()) {
            ArraySort.sort(arrays[0], argument.getOrder().getComparator());
            ArraySort.sort(arrays[1], argument.getOrder().getComparator());
        }
        return arrays;
    }

    public static Object[] collectionObjFactory(Argument argument) throws UnableResolveTypeException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Object[] collections = new Object[2];
        int size = argument.getFixSize() == -1 ? (int) Range.getNumberMinToMax(argument.getSize()) : argument.getFixSize();
        if (size < 0) {
            return null;
        }
        collections[0] = ReflectUtil.getNonParameterInstance(argument.getClassName());
        collections[1] = ReflectUtil.getNonParameterInstance(argument.getClassName());
        for (int i = 0; i < size; i++) {
            Object[] objs = factory(argument.getChild()[0]);
            assert objs != null;
            ((Collection) (collections[0])).add(objs[0]);
            ((Collection) (collections[1])).add(objs[1]);
        }

        if (argument.getOrder() != null && argument.getOrder().getIsSorted()) {
            List list1;
            List list2;
            if (collections[0] instanceof List) {
                list1 = (List) collections[0];
                list2 = (List) collections[1];
                list1.sort(argument.getOrder().getComparator());
                list2.sort(argument.getOrder().getComparator());
            } else {
                list1 = new ArrayList(((Collection) (collections[0])));
                list2 = new ArrayList(((Collection) (collections[1])));
                list1.sort(argument.getOrder().getComparator());
                list2.sort(argument.getOrder().getComparator());
                ((Collection) (collections[0])).clear();
                ((Collection) (collections[1])).clear();
                ((Collection) (collections[0])).addAll(list1);
                ((Collection) (collections[1])).addAll(list2);
            }
        }
        return collections;
    }

    public static Object[] mapObjFactory(Argument config) throws UnableResolveTypeException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Object[] maps = new Object[2];
        int size = config.getFixSize() == -1 ? (int) Range.getNumberMinToMax(config.getSize()) : config.getFixSize();
        if (size < 0) {
            return null;
        }
        // 构造两个Map
        maps[0] = ReflectUtil.getNonParameterInstance(config.getClassName());
        maps[1] = ReflectUtil.getNonParameterInstance(config.getClassName());
        // 获取足够数量的 Key -> Value
        for (int i = 0; i < size; i++) {
            // 返回两个建
            Object[] keys = factory(config.getChild()[0]);
            Object[] values = factory(config.getChild()[1]);
            assert keys != null;
            assert values != null;
            ((Map) ((maps[0]))).put(keys[0], values[0]);
            ((Map) ((maps[1]))).put(keys[1], values[1]);
        }

        // 如果没有收集到足够的key，那么继续构造
        int count = size - ((Map) (maps[0])).size();
        if (count > 0) {
            do {
                // 继续尽最大努力构造key
                Object[] keys = factory(config.getChild()[0]);
                assert keys != null;
                if (!((Map) (maps[0])).containsKey(keys[0])) {
                    Object[] values = factory(config.getChild()[1]);
                    assert values != null;
                    ((Map) (maps[0])).put(keys[0], values[0]);
                    ((Map) (maps[1])).put(keys[1], values[1]);
                    count--;
                }
            } while (count > 0);
        }
        return maps;
    }
}